<?php

/**
 * Абстрактный родитель для объектов модулей
 */
abstract class Module {

	protected $moduleName = '';
	protected static $baseMethods = array();
	protected static $subMethods = array ();
	protected static $subObjects = array ();

	protected function __construct ($moduleName) {
		$this->moduleName = $moduleName;
		
		self::$baseMethods = get_class_methods($moduleName);
	}
	
	final public function __clone () {
		throw new CoreException (lang('error_module_clone',__CLASS__));
	}
	
	public function __call ($method, $params) {
		if (!isset (self::$subMethods[$this->moduleName][$method])) {
			if (in_array($method, self::$baseMethods)) {
				$oBaseModuleClass = CMSController::getInstance()->getModule($this->moduleName);
				
				return call_user_func_array(array($oBaseModuleClass, $method), $params);
			}
			else {
				throw new NotFound;
			}
		}
		
		$className = self::$subMethods[$this->moduleName][$method];
		
		$oRefMethod = new ReflectionMethod ($className, $method);
		if (!$oRefMethod->isPublic() || $oRefMethod->isStatic()) {
			throw new NotFound;
		}
		
		/*$sParams = '';
		$cntParams = count ($params);
		
		for ($i = 0; $i < $cntParams; ++$i) {
			$sParams .= '$params[' . $i . ']';
			if ($i != $cntParams - 1) {
				$sParams .= ', ';
			}
		}*/
		
		return call_user_func_array(array(self::$subObjects[$this->moduleName][$className], $method), $params);
		//eval ('$response = self::$subObjects[$this->moduleName][\'' . $className . '\']->' . $method . ' (' . $sParams . ');');
		//return $response;
	}
	
	/**
	 * Возвращает реальное название класса, где реализован заданный метод
	 * @param string $method
	 * @return string
	 */
	public function getMethodClassName ($method) {
		if (isset (self::$subMethods[$this->moduleName])) {
			if (isset (self::$subMethods[$this->moduleName][$method])) {
				return self::$subMethods[$this->moduleName][$method];
			}
		}
		
		if (method_exists ($this, $method)) {
			return $this->moduleName;
		}
		
		return false;
		//return isset (self::$subMethods[$this->moduleName][$method]) ? self::$subMethods[$this->moduleName][$method] : false;
	}
	
	/**
	 * Подгружает заданных класс. При отсутствии вызванного метода у главного класса модуля будет вызван метод подгруженного класса
	 * @param string $sClassName имя класса, файл этого класса должен иметь такое же название и находиться в одной папке с текущим модулем
	 * @return void
	 */
	protected function extend ($className) {
		$file = ENGINE_ROOT . '/modules/' . $this->moduleName . '/' . $className . '.php';
		
		if (!file_exists ($file)) {
			trigger_error (lang('error_try_extend_not_exists_file',__CLASS__) . $file, E_USER_ERROR);
		}
		
		include $file;
		
		if (!class_exists ($className, false)) {
			trigger_error (lang('error_try_extend_class_not_exists',__CLASS__) . $className, E_USER_ERROR);
		}
		
		self::$subObjects[$this->moduleName][$className] = new $className;
		
		$methods = get_class_methods($className);
		
		foreach ($methods as $methodName) {
			self::$subMethods[$this->moduleName][$methodName] = $className;
		}
	}
	
	/**
	 * Проверяет, существует ли метод у данного модуля, учитывая множественное наследование
	 * @param string $sMethodName
	 * @return boolean
	 */
	public function isMethodExists ($sMethodName) {
		if (isset (self::$subMethods[$this->moduleName])) {
			if (isset (self::$subMethods[$this->moduleName][$sMethodName])) {
				return true;
			}
		}
		
		if (method_exists ($this, $sMethodName)) {
			return true;
		}
		
		/*if (isset (self::$subMethods[$this->moduleName])) {		
			foreach (self::$subMethods[$this->moduleName] as $sSubName => $c) {
				if ($sMethodName == $sSubName) {
					return true;
				}
			}
		}*/
		
		return false;
	}
	
	/**
	 * Возвращает массив значений полей, переданных POST запросом, относящихся к объекту
	 * @param integer $objectId id объекта
	 * @param Type $type тип
	 * @param boolean $bGetNotEditable принимать значения полей, запрещенных для редактирования через форму
	 * @return array массив: ключ - имя поля, значение - значение
	 */
	protected function getRequestData ($objectId, Type $type, $bGetNotEditable = false) {
		$aFields = $type->getFields ();
		$response = array ();
		
		foreach ($aFields as $fieldName => $oField) {
			if (!$oField->isReal () || (!$oField->isEditable () && !$bGetNotEditable)) {
				continue;
			}
			
			if ($oField->getCharacter () == 'boolean') {
				if (isset ($_POST['data'][$objectId][$fieldName])) { 
					$response[$fieldName] = 1;
				} else {
					$response[$fieldName] = 0;
				}
				continue;
			}
			
			if (isset ($_POST['data'][$objectId][$fieldName])) {
				$character = $oField->getCharacter ();
				
				if ($character == 'guide' || $character == 'multiguide') {
					$response[$fieldName] = array ();
					
					if (is_array ($_POST['data'][$objectId][$fieldName])) {
						foreach ($_POST['data'][$objectId][$fieldName] as $value) {
							$value = (int) $value;
							if ($value > 0) {
								$response[$fieldName][] = $value;
							}
						}
					}
				}
				elseif ($character == 'tags') {
					$requestString = trim ((string) $_POST['data'][$objectId][$fieldName]);
					
					$aGarbage = explode (',', $requestString);
					$aGarbage = array_unique ($aGarbage);
					
					$response[$fieldName] = array ();
					
					foreach ($aGarbage as $value) {
						$value = trim ($value);
						if ($value != '') {
							$response[$fieldName][] = $value;
						}
					}
				}
				elseif ($character == 'birthdate' || $character == 'date') {
					$requestValue = $_POST['data'][$objectId][$fieldName];
					
					if ($requestValue == '') {
						$response[$fieldName] = 0;
					} else {
						$response[$fieldName] = strtotime ($_POST['data'][$objectId][$fieldName]);
					}
				} else {
					$response[$fieldName] = (string) $_POST['data'][$objectId][$fieldName];
				}
			} else {
				continue;
			}
		}
		
		return $response;
	}
	
	/**
	 * Отдает браузеру контент и заверщает программу
	 * @param mixed $content контент
	 * @param string $contentType тип контента
	 * @return void
	 */
	protected function flush ($content, $contentType = 'text/html') {
		header ("Content-type: " . $contentType . "; charset=utf-8");
		
		print $content;
		
		exit;
	}
	
	/**
	 * Устанавливает заголовок страницы
	 * @param string $sTitle
	 * @return void
	 */
	protected function setPageTitle ($sTitle) {
		if ($sTitle) {
			CMSController::setPageTitle ($sTitle);
		}
	}
	
	/**
	 * Проверяет значения свойств объекта на соответствие полям определенного типа
	 * @param Object $object
	 * @return array массив ошибок полей, где ключ - название поля
	 */
	protected function getValidateErrors (Object $object) {
		$oType = $object->getType (); 
		
		$aFields = $oType->getFields ();
		$aValues = $object->getValues ();
		
		$aErrors = array ();
		 
		foreach ($aFields as $fieldName => $oField) {
			$character = $oField->getCharacter ();
			
			if ($oField->isRequired () && $oField->isReal ()) {		 		
		 		// Теги разрешены только в тексте
				if ($character != 'text' && isset($aValues[$fieldName]) && !is_array($aValues[$fieldName])) {
		 			$aValues[$fieldName] = strip_tags($aValues[$fieldName]);
		 		}				
				
				// Проверка загрузки изображения. Хня, но тут ничего не поделаешь =\
		 		if ($character == 'image') {
		 			// Новый объект
		 			if ($object->getId () == 0) {
		 				if (!isset ($aValues[$fieldName]) || $aValues[$fieldName] == '') {
			 				if ((isset ($_FILES['data']['tmp_name']['new'][$fieldName]) && $_FILES['data']['tmp_name']['new'][$fieldName] == '') || !isset ($_FILES['data']['tmp_name']['new'][$fieldName])) {
			 					$aErrors[$fieldName] = $oField->getErrorMessage ();
			 					continue;
			 				}
		 				}
		 			}
		 			// Не новый
		 			else {
		 				if (!isset ($aValues[$fieldName])) {
				 			$aErrors[$fieldName] = $oField->getErrorMessage ();
				 			continue;
				 		}
		 			}
		 			continue;
		 		}
		 		
		 		// Пустое значение
		 		if (!isset ($aValues[$fieldName]) || $aValues[$fieldName] === '') {
		 			$aErrors[$fieldName] = $oField->getErrorMessage ();
		 			continue;
		 		}
		 		
		 		// что-то про справочники
		 		if (($character == 'guide' || $character == 'multiguide') && (!is_array ($aValues[$fieldName]) || count ($aValues[$fieldName]) == 0)) {
		 			$aErrors[$fieldName] = $oField->getErrorMessage ();
		 			continue;
		 		}
		 		
		 		// не заполненные теги
		 		if ($character == 'tags' && ($aValues[$fieldName] === '' || count ($aValues[$fieldName]) == 0)) {
		 			$aErrors[$fieldName] = $oField->getErrorMessage ();
		 			continue;
		 		}
		 		
		 		// ограничение длины
		 		if (is_numeric ($oField->getMaxlength ())) {
		 			if (mb_strlen ($aValues[$fieldName]) > (int) $oField->getMaxlength ()) {
		 				$aErrors[$fieldName] = lang ('validation_error_value_so_long',__CLASS__) . ' (' . $oField->getMaxlength() . ')';
		 			}
		 		}
		 	}
		 	
		 	$fieldValue = isset ($aValues[$fieldName]) ? $aValues[$fieldName] : '';
		 	
		 	if ($fieldValue == '') {
		 		continue;
		 	}
		 	
		 	// Проверка некоторых форматов
		 	switch ($character) {
		 		case 'integer' : {
					if (!is_numeric ($fieldValue)) {
						$aErrors[$fieldName] = lang ('validation_error_value_int',__CLASS__);
					}
					break;
				}
				case 'latin' : {
					if (!preg_match("#^[a-zA-Z0-9_-]+$#", $fieldValue)) {
						$aErrors[$fieldName] = lang ('validation_error_value_latin',__CLASS__);
					}
					break;
				}
				case 'email' : {
					if (!preg_match('/^([a-zA-Z0-9])+([a-zA-Z0-9\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$/', $fieldValue)) {
						$aErrors[$fieldName] = lang ('validation_error_value_email',__CLASS__);
					}
					break;
				}
				default : {
					continue;
				}
		 	}
		 }
		 
		 return $aErrors;
	}
	
	/**
	 * Добавляет фильтры в Selection, переданные в URI
	 * @param Type $oType тип, поля которого будут искаться в фильтрах
	 * @param Selection $selection
	 * @return void
	 */
	protected function detectFilters (Type $oType, Selection $selection) {
		$aFields = $oType->getFields ();
		
		if (!isset ($_REQUEST['filter']) || !is_array ($_REQUEST['filter'])) {
			return;
		}
		
		$aFilters = $_REQUEST['filter'];
		
		foreach ($aFilters as $fieldName => $value) {			
			if (!is_array ($value) && isset ($aFields[$fieldName]) && $aFields[$fieldName]->isSearch () && $aFields[$fieldName]->isReal ()) {
				$selection->addFilter ($fieldName, $value);
			}
		}
	}
	
	/**
	 * Бросает исключение AccessException при неверном GET-параметре scode
	 * @return void
	 */
	protected function expectSecureGET () {
		$requestValue = getRequest ('scode');
		
		if (SCODE != $requestValue) {
			throw new AccessException (lang('error_scode',__CLASS__));
		}
	}
	
	protected function setResponseFormat ($format) {
		PageParser::getInstance()->setCurrentFormat ($format);
	}

}